第 2 步 - 实现拖放功能

这一步中,您将实现拖放功能。

创建拖动功能

在本节中,您将首先使用 Kanzi Engine API 实例化您在本教程上一步创建的 Drag Item 预设件模板。然后定义用户开始拖放手势和拖动按钮时的行为。最后,您将创建并配置导航栏中各按钮的拖放操纵器。

要创建拖动功能:

  1. 在 Visual Studio 中,在drag_and_drop.cpp 文件的 onProjectLoaded() 函数中实例化用于可视化用户正在拖动的按钮的Drag Item 预设件:
        virtual void onProjectLoaded() KZ_OVERRIDE
        {
            ...
    
            //获取 Drag Item 预设件的引用。
            ResourceManager* resourceManager = getDomain()->getResourceManager();
            PrefabTemplateSharedPtr dragItemPrefab = resourceManager->acquireResource<PrefabTemplate>("kzb://drag_and_drop/Prefabs/Drag Item");
    
            //实例化 Drag Item 预设件。
            m_dragItem = dragItemPrefab->instantiate<Node2D>("Drag Item");
    
            //获取使用其别名的 RootPage 节点。
            Node2DSharedPtr rootPage = screen->lookupNode<Node2D>("#RootPage");
    
            //为RootPage 节点添加您创建的 Drag Item 预设件实例。
            rootPage->addChild(m_dragItem);
    
            //禁用 Drag Item 预设件实例的可见性 (Visible) 属性。
            //当用户不拖动 Drag Item 时,将其隐藏。
            m_dragItem->setVisible(false);
        }
    
    private: 
        ...
    
        //为实例化的 Drag Item 定义成员变量。
        Node2DSharedPtr m_dragItem;
    
  2. DragAndDrop 类的私有部分创建移动 Drag Item 预设件实例的函数:
    private: 
    
        ...
    
        //更新 Drag Item 的位置。
        void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
        {
            //计算局部拖动锚点,即用户正在拖动的按钮左上角。
            Vector2 localDragAnchor = dragPosition - m_dragGrabOffset;
    
            //将 Drag Item 的移动限制到 x 轴。
            localDragAnchor.setY(0.0f);
    
            //计算全局拖动锚点。
            Vector2 globalDragAnchor = dragWorldTransform * localDragAnchor;
    
            //用于描述 Drag Item渲染变换 (Render Transformation) 属性的结构。
            SRTValue2D transform;
            //设置渲染变换 (Render Transformation) 属性平移 (Translation) 属性字段为全局拖动锚点。
            transform.setTranslation(globalDragAnchor);
    
            //移动 Drag Item 拖动的距离量。
            m_dragItem->setRenderTransformation(transform);
    
            //设置按钮图标。
            updateItems();
        }
    
        ...
    
        //定义用户按下或点击按钮时该按钮左上角偏移的成员变量。
        Vector2 m_dragGrabOffset;
    };
    
  3. 为拖动消息添加处理程序:
    1. 要定义用户开始拖动按钮时的行为,在 DragAndDrop 类的私有部分定义 DragAndDropManipulator::StartedMessage 消息的处理程序:
      private: 
      
          ...
      
          // 定义 2D 节点的 DragAndDropManipulator::StartedMessage 消息处理程序 
          //这些节点带有生成拖放消息的输入操纵器。
          //这样可为拖动准备 2D 节点。
          void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
          {
              //从消息参数获得用户开始拖动的按钮。
              Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
      
              //获取用户开始拖动的按钮大小。
              Vector2 dragSourceItemSize = dragSourceItem->getActualSize();
      
              //将 Drag Item 设置为和该按钮相同的大小。
              m_dragItem->setSize(dragSourceItemSize.getX(), dragSourceItemSize.getY());
      
              //移动 Drag Item 到正确的位置。
              updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
      
              //让 Drag Item 可见。
              m_dragItem->setVisible(true);
          }
      
          ...
      
    2. 要定义用户正在拖动按钮时的行为,在 onDragStarted 函数后面定义 DragAndDropManipulator::MovedMessage 消息的处理程序:
          // 定义 2D 节点的 DragAndDropManipulator::MovedMessage 消息处理程序 
          //这些节点带有生成拖放消息的输入操纵器。
          void onDragMoved(DragAndDropManipulator::MovedMessageArguments& messageArguments)
          {
              //从消息参数获得用户正在拖动的按钮。
              Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
      
              //移动 Drag Item 并更新按钮图标。
              updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
          }
  4. 创建并配置各按钮的拖放操纵器:
    1. DragAndDrop 类的私有部分添加创建和配置节点拖放操纵器的函数:
          // 创建并配置节点的拖放操纵器。
          void createDragAndDropManipulator(NodeSharedPtr dragSourceItem)
          {
              Domain* domain = getDomain();
      
              //创建生成拖放消息的输入操纵器。
              DragAndDropManipulatorSharedPtr dragAndDropManipulator = DragAndDropManipulator::create(domain);
      
              //添加输入操纵器到该节点。
              dragSourceItem->addInputManipulator(dragAndDropManipulator);
      
              //在拖放开始前设置长按持续时间为 200 ms。默认值为 500 ms。
              //这是用户在开始拖动节点前必须按下节点的时间。
              dragAndDropManipulator->setPressDuration(chrono::milliseconds(200));
      
              //订阅该节点的 DragAndDropManipulator::StartedMessage 消息。
              // DragAndDropManipulator 在用户按下该节点
              //获取 DragAndDropManipulator::setPressDuration 设置的持续时间时生成此消息。
              dragSourceItem->addMessageHandler(DragAndDropManipulator::StartedMessage, bind(&DragAndDrop::onDragStarted, this, placeholders::_1));
      
              //订阅该节点的 DragAndDropManipulator::MovedMessage 消息。
              // DragAndDropManipulator 在指针移动时生成此消息。
              dragSourceItem->addMessageHandler(DragAndDropManipulator::MovedMessage, bind(&DragAndDrop::onDragMoved, this, placeholders::_1));
          }
    2. onProjectLoaded() 函数末尾为各按钮调用 createDragAndDropManipulator 函数:
          virtual void onProjectLoaded() KZ_OVERRIDE
          {
              ...
      
              //创建各按钮的拖放操纵器。
              //使用别名获取按钮节点。
              createDragAndDropManipulator(screen->lookupNode<Node>("#Navigation"));
              createDragAndDropManipulator(screen->lookupNode<Node>("#Phone"));
              createDragAndDropManipulator(screen->lookupNode<Node>("#Applications"));
              createDragAndDropManipulator(screen->lookupNode<Node>("#Music"));
              createDragAndDropManipulator(screen->lookupNode<Node>("#Car"));
          }
  5. 构建和运行应用程序。
    在应用程序中长按按钮,并在水平方向拖动。
    您拖动 Drag Item 预设件的实例。Drag Item 左侧定位在输入指针,Drag Item 尚未显示图标。在下一节中,您将完成拖动功能。

完成拖动功能

这一节中,您将设置用户拖动的按钮图标和位置,并在用户拖动其中一个按钮时重定位按钮图标。

要完成拖动功能:

  1. onDragStarted 函数中,在调用 updateDragAndDrop 函数之前,设置 Drag Item 的图标并正确定位该节点:
    void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
        {
            ...
    
            //获取用户开始拖动的按钮的数据上下文对象。
            m_draggedDataContext = dynamic_pointer_cast<DataObject>(dragSourceItem->getProperty(DataContext::DataContextProperty));
    
            //将 Drag Item数据上下文 (Data Context) 属性设置为该按钮的数据上下文。
            //这样即可将 Drag Item 设置为具有和用户开始拖动的按钮相同的图标。
            m_dragItem->setProperty(DataContext::DataContextProperty, m_draggedDataContext);
    
            //保存用户相对于节点原点(默认情况下为左上角)
            //开始拖动节点的起点。
            m_dragGrabOffset = messageArguments.getPoint();
    
            ...
        }
    
        ...
    
        //为用户拖动的按钮的数据上下文对象定义成员变量。
        DataObjectSharedPtr m_draggedDataContext;
    };
    

  2. updateDragAndDrop 函数中,在调用 updateItems() 函数之前,添加代码,在用户拖动其中一个按钮时重定位所有按钮的图标:
        void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
        {
            ...
    
            //获取全局指针位置。
            Vector2 globalPointerPosition = dragWorldTransform * dragPosition;
    
            //将全局坐标转换为 2D 网格布局 (Grid layout 2D) 节点的本地坐标。
            Vector2 hitTestPoint = *m_grid->globalToLocal(globalPointerPosition);
    
            //获取按钮的宽度。
            float cellWidth = m_grid->getActualColumnSize(0);
    
            //计算2D 网格布局 (Grid layout 2D) 节点中按钮的索引。
            unsigned int cellIndex = 0;
    
            if (hitTestPoint.getX() > 0.0f)
            {
                cellIndex = static_cast<unsigned int>(hitTestPoint.getX() / cellWidth);
                cellIndex = min(cellIndex, m_grid->getChildCount() - 1u);
            }
    
            //从旧位置移除数据对象。
            m_rootData->removeChild(*m_draggedDataContext);
    
            //插入数据对象到新位置。
            m_rootData->insertChild(cellIndex, m_draggedDataContext);
    
            ...
        }
    

  3. updateItems() 函数中,在 for 循环中添加 if-else 从句,在用户拖动时隐藏按钮图标:
        void updateItems()
        {
            ...
    
            for (; dataIt != endDataIt; dataIt++, nodeIt++)
            {
                ...
    
                //如果按钮节点是用户正在拖动的节点,将其隐藏。
                //隐藏该节点,因为使用 Drag Item 可视化该节点的拖动。
                if (m_draggedDataContext && itemData == m_draggedDataContext)
                {
                    itemNode->setVisible(false);
                }
                else
                {
                    itemNode->setVisible(true);
                }
            }
        }

创建放置功能

在之前的部分,实现了按钮的拖放。当用户结束拖放手势时,Drag Item 在用户释放指针的位置保持可见。在本节中,您将添加代码,让按钮看上去像落入用户放置它的位置。

要创建放置功能:

  1. 要定义用户停止拖动节点并释放指针时的行为,在 onDragMoved 函数后面定义 DragAndDropManipulator::FinishedMessage 消息的处理程序:
        // 定义 2D 节点的 DragAndDropManipulator::FinishedMessage 消息处理程序 
        //这些节点带有生成拖放消息的输入操纵器。
        void onDragFinished(DragAndDropManipulator::FinishedMessageArguments&)
        {
            //隐藏 Drag Item 预设件实例。
            m_dragItem->setVisible(false);
    
            //清除至数据上下文对象的指针。
            m_draggedDataContext.reset();
    
            //分配正确的图标到按钮。
            updateItems();
        }
  2. createDragAndDropManipulator 函数的末尾订阅该节点的 DragAndDropManipulator::FinishedMessage 消息:
        // 创建并配置节点的拖放操纵器。
        void createDragAndDropManipulator(NodeSharedPtr dragAndDropNode)
        {
            ...
    
            //订阅该节点的 DragAndDropManipulator::FinishedMessage 消息。
            //当用户松开指针结束拖放手势时,DragAndDropManipulator 
            //生成此消息。
            dragSourceItem->addMessageHandler(DragAndDropManipulator::FinishedMessage, bind(&DragAndDrop::onDragFinished, this, placeholders::_1));
        }
  3. 构建和运行应用程序。
    结束拖放手势时,Drag Item 变得不可见。


< 上一步
下一步 >

另请参阅

要详细了解拖放输入操纵器,请参阅使用拖放操纵器

要详细了解关于在 Kanzi 中处理用户输入的详细信息,请参阅处理用户输入